#include "Shader.h"
#include <fstream>
#include <iostream>

namespace lazy {

    Shader::Shader() {

    }

    Shader::~Shader() {
        glDeleteProgram(m_ProgramID);
    }

    void Shader::Bind() {
        glUseProgram(m_ProgramID);
    }

    void Shader::Unbind() {
        glUseProgram(0);
    }

    Ref<Shader> Shader::LoadFromFile(const std::filesystem::path &vertFilePath, const std::filesystem::path &fragFilePath) {
        auto shader = CreateRef<Shader>();
        shader->InitProgram(vertFilePath, fragFilePath);
        return shader;
    }

    std::string Shader::ReadFile(const std::filesystem::path &filePath) {
        std::string result;
        std::ifstream in(filePath, std::ios::in | std::ios::binary);
        if (in) {
            in.seekg(0, std::ios::end);
            result.resize(in.tellg());
            in.seekg(0, std::ios::beg);
            in.read(&result[0], result.size());
            in.close();
        }
        return result;
    }

    void Shader::InitProgram(const std::filesystem::path &vertFilePath, const std::filesystem::path &fragFilePath) {
        std::string vSourceStr = ReadFile(vertFilePath);
        std::string fSourceStr  = ReadFile(fragFilePath);
        const char* vShaderCode = vSourceStr.c_str();
        const char* fShaderCode = fSourceStr.c_str();
        unsigned int vertex, fragment;

        vertex = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex, 1, &vShaderCode, nullptr);
        glCompileShader(vertex);
        CheckCompileErrors(vertex, "VERTEX");

        fragment = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragment, 1, &fShaderCode, NULL);
        glCompileShader(fragment);
        CheckCompileErrors(fragment, "FRAGMENT");

        m_ProgramID = glCreateProgram();
        glAttachShader(m_ProgramID, vertex);
        glAttachShader(m_ProgramID, fragment);
        glLinkProgram(m_ProgramID);
        CheckCompileErrors(m_ProgramID, "PROGRAM");

        glDeleteShader(vertex);
        glDeleteShader(fragment);
    }

    void Shader::CheckCompileErrors(GLuint shader, const std::string &type) {
        GLint success;
        GLchar infoLog[1024];
        if (type != "PROGRAM") {
            glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
            if (!success) {
                glGetShaderInfoLog(shader, 1024, nullptr, infoLog);
                std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
            }
        } else {
            glGetProgramiv(shader, GL_LINK_STATUS, &success);
            if (!success) {
                glGetProgramInfoLog(shader, 1024, nullptr, infoLog);
                std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
            }
        }
    }

    void Shader::SetBool(const std::string &name, bool value) {
        glUniform1i(glGetUniformLocation(m_ProgramID, name.c_str()), (int)value);
    }

    void Shader::SetInt(const std::string &name, int value) {
        glUniform1i(glGetUniformLocation(m_ProgramID, name.c_str()), value);
    }

    void Shader::SetIntArray(const std::string &name, int *values, uint32_t count) {
        glUniform1iv(glGetUniformLocation(m_ProgramID, name.c_str()), count, values);
    }

    void Shader::SetFloat(const std::string &name, float value) {
        glUniform1f(glGetUniformLocation(m_ProgramID, name.c_str()), value);
    }

    void Shader::SetFloat2(const std::string &name, const glm::vec2 &vec) {
        glUniform2fv(glGetUniformLocation(m_ProgramID, name.c_str()), 1, &vec[0]);
    }

    void Shader::SetFloat3(const std::string &name, const glm::vec3 &vec) {
        glUniform3fv(glGetUniformLocation(m_ProgramID, name.c_str()), 1, &vec[0]);
    }

    void Shader::SetFloat4(const std::string &name, const glm::vec4 &vec) {
        glUniform4fv(glGetUniformLocation(m_ProgramID, name.c_str()), 1, &vec[0]);
    }

    void Shader::SetMat2(const std::string &name, const glm::mat2 &mat) {
        glUniformMatrix2fv(glGetUniformLocation(m_ProgramID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }

    void Shader::SetMat3(const std::string &name, const glm::mat3 &mat) {
        glUniformMatrix3fv(glGetUniformLocation(m_ProgramID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }

    void Shader::SetMat4(const std::string &name, const glm::mat4 &mat) {
        glUniformMatrix4fv(glGetUniformLocation(m_ProgramID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }

} // lazy